module VirtualCityScripts.Planner

open Casanova
open Casanova.Core
open Casanova.Coroutines
open Casanova.Game
open Casanova.Input
open Casanova.Math
open Casanova.Drawing
open VirtualCity.City
open VirtualCity.CityDescription
open VirtualCity.Plan


(*
script for:
- learning stuff (where do we store knowledge? atm in the script itself, but better in the agent
- learning higher-level planners
*)

let preconditions_ok clock (action:Action) agent =
  match action with
  | Walk road -> true
  | Board bus -> bus.TicketPrice <= agent.Money
  | Use item ->  
      item.UseSlots.Contains(clock.Time) && 
      item.StatsLost <= agent.Stats &&
      item.ItemsLost <= agent.Inventory &&
      item.MoneyLost <= agent.Money


let wait_until_doable clock action agent =
  co{
    match action with
    | Walk -> do! yield_
    | Board bus -> do! wait_condition (fun () -> distance(bus,agent) <= 3.0f<m>)
    | Use item -> do! wait_condition (fun () -> item.UseSlots.Contains(clock.Time))
  }


let take_step city agent step =
  let action = step.Action
  let expectations = step.Expectations
  let clock = city.Clock
  co{
    if agent |> preconditions_ok clock action then
      do! agent |> wait_until_doable clock action
      do! agent |> perform action
  } .||> wait_condition (fun () -> (city,agent) |> too_different_from expectations)


let follow_plan city agent =
  let plan = agent.ActivePlan
  co{
    for step in plan.Actions do 
      take_step city agent step
  }


let is_dead agent =
  agent.Stats >= 0.0


let is_acceptable plan =
  plan.Steps.Head.Expectations.Agent |> is_dead |> not


let find_plan city agent check_goal =  
  let Q = Queue.Empty
  let add_to_q plan = 
    if plan |> is_acceptable then
      Q.add plan
  let add_next_action city plan =
    for item in agent |> items_nearby city do
      add_to_q { plan with Steps = perform_action city (Use item) :: plan.Steps }
    for road in agent |> roads_nearby city do
      add_to_q { plan with Steps = perform_action city (Walk road) :: plan.Steps }
    for bus in agent |> buses_nearby city do
      add_to_q { plan with Steps = perform_action city (Board bus) :: plan.Steps }
  do add_next_action city { Steps = [] }
  let rec explore_plan() = 
    co{
      if Q.is_empty then 
        return None
      else
        let plan = Q.dequeue()
        if plan |> check_goal then
          return Some plan
        else
          do add_next_action (plan |> get_expected_city) plan
          do! yield_
          return! explore_plan()
    }
  explore_plan()


let create_plan city agent =
  co{
    let city_description = city |> get_city_description
    let agent_description = agent |> get_agent_description
    let! plan = agent_description |> find_plan city_description
    match plan with
    | Some plan -> agent.ActivePlan := plan
    | None -> agent.ActivePlan := { Steps = [] }
  }


let agent_plan_routine city agent =
  co{
    do! agent |> plan city
    do! agent |> create_plan city
  } |> repeat_ .||> wait_condition (fun () -> agent |> is_dead)
